﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using Abp.Auditing;
using Abp.Configuration;
using Abp.Dependency;
using Abp.Domain.Repositories;
using Abp.Domain.Uow;
using Abp.EntityFrameworkCore.EFPlus;
using Abp.Logging;
using Abp.Threading;
using Abp.Threading.BackgroundWorkers;
using Abp.Threading.Timers;
using Abp.Timing;
using pandx.Mulan.MultiTenancy;
using pandx.Mulan.Settings;

namespace pandx.Mulan.Auditing
{
    public class ExpiredAuditLogDeleteWorker : PeriodicBackgroundWorkerBase, ISingletonDependency
    {
        public const bool IsEnabled = true;
        private const int CheckPeriodAsMilliseconds = 3* 60 * 1000; //3 min
        private const int MaxDeletionCount = 10000;
        private readonly TimeSpan _logExpireTime;
        private readonly IRepository<AuditLog, long> _auditLogRepository;
        private readonly IRepository<Tenant> _tenantRepository;
        private readonly SettingManager _settingManager;
        public ExpiredAuditLogDeleteWorker(AbpTimer timer, IRepository<AuditLog, long> auditLogRepository,
            IRepository<Tenant> tenantRepository,SettingManager settingManager) : base(timer)
        {
            _auditLogRepository = auditLogRepository;
            _tenantRepository = tenantRepository;
            _settingManager = settingManager;
            _logExpireTime =
                TimeSpan.FromDays(Convert.ToInt32(
                    _settingManager.GetSettingValue(AppSettings.PeriodicBackgroundWorker
                        .ExpireAuditLogDelete)));
            Timer.Period = CheckPeriodAsMilliseconds;
            Timer.RunOnStart = true;
        }

        protected override void DoWork()
        {
            var expireDate = Clock.Now - _logExpireTime;
            List<int> tenantIds;
            using(var uow = UnitOfWorkManager.Begin()){
                tenantIds = _tenantRepository.GetAll().Where(t => !string.IsNullOrEmpty(t.ConnectionString))
                    .Select(t => t.Id).ToList();
                uow.Complete();
            }

            DeleteAuditLogsOnHostDatabase(expireDate);
            foreach (var tenantId in tenantIds)
            {
                DeleteAuditLogsOnTenantDatabase(tenantId, expireDate);
            }
        }

        protected virtual void DeleteAuditLogsOnHostDatabase(DateTime expireDate)
        {
            try
            {
                using (var uow = UnitOfWorkManager.Begin())
                {
                    using (CurrentUnitOfWork.SetTenantId(null))
                    {
                        DeleteAuditLogs(expireDate);
                            uow.Complete();
                    }
                }
            }
            catch (Exception e)
            {
                Logger.Log(LogSeverity.Error, "删除主数据库审计日志时发生错误", e);
            }
        }

        protected virtual void DeleteAuditLogsOnTenantDatabase(int tenantId, DateTime expireDate)
        {
            try
            {
                using (var uow = UnitOfWorkManager.Begin())
                {
                    using (CurrentUnitOfWork.SetTenantId(tenantId))
                    {
                        DeleteAuditLogs(expireDate);
                        uow.Complete();
                    }
                }
            }
            catch (Exception e)
            {
                Logger.Log(LogSeverity.Error, $"删除租户{tenantId}数据库审计日志时发生错误", e);
            }
        }

        private void DeleteAuditLogs(DateTime expireDate)
        {
            var expireEntryCount = _auditLogRepository.LongCount(l => l.ExecutionTime < expireDate);
            if (expireEntryCount == 0)
            {
                return;
            }

            if (expireEntryCount > MaxDeletionCount)
            {
                var deleteStartId = _auditLogRepository.GetAll().OrderBy(l => l.Id).Skip(MaxDeletionCount)
                    .Select(x => x.Id).First();
                AsyncHelper.RunSync(() => _auditLogRepository.BatchDeleteAsync(l => l.Id < deleteStartId));
            }
            else
            {
                AsyncHelper.RunSync(() => _auditLogRepository.BatchDeleteAsync(l => l.ExecutionTime < expireDate));
            }
            
        }
    }
}